#include "mboard.h"
#include "mboard_private.h"
#include "spi_hw.h"
#include "gpio_hw.h"
#include "dma_hw.h"
#include "rcc.h"
#include "nvic.h"
#include "mdebug.h"
#include "mgdb.h"
#include <string.h>
#include "umm_malloc.h"

/**
 * W25Q128，16MB，供电电压2.7~3.6V@最大25mA，板上WP/HOLD被连接到VCC
 *   1、分区信息，擦写支持暂停和继续
 *       1、PAGE --- 256B(65536) ---- 一次可写1到256字节数据
 *       2、SECTOR - 4KB(16p,4096) -- 扇区擦除
 *       3、BLOCK -- 32KB(128p,512) - 64KB(256p,256) -- 块擦除
 *       4、CHIP --- 16M(65536p,1) -- 片擦除
 *       5、SECURE - 256B*3 --------- 3个安全寄存器，可独立进行擦写/保护，不受
 *          片擦影响
 *   2、支持单线、双线、四线SPI以及QPI接口，最高50MB/s（QSPI）连续数据传输，
 *      10W次擦写，20年数据保持
 *       1、单线：DI用于指令、地址、数据输入上升沿采样，MSB，DO用于状态、
 *          数据输出下降沿采样;
 *       2、四线操作需要将SR2的QE设置为1，表示/WP和/HOLD被复用为IO2和IO3;
 *   3、支持软件和硬件的写保护，带有3*256bitsOTP区域，64bits唯一ID
 *       1、/WP低电平有效，禁止修改SR寄存器，可以实现至少4KB或所有区域的写保
 *          护，该引脚在4线模式下无效;
 *   4、/HOLD低电平有效，当/CS拉低时，拉低/HOLD，等到CLK为低时，DO会变为高
 *      阻态，CLK和DI上的信号被忽略，当/HOLD再拉高后，等到CLK为低时，可继续
 *      之前的操作，期间/CS应保持为低，该引脚在4线模式下无效;
 *   5、/HOLD和/RESET的功能选择由SR3寄存器配置，/RESET(持续1us)可用于复位
 *      Flash，发送66h(复位使能)+99h(复位)也可以实现复位操作，Flash复位后
 *      需要30us的启动时间，复位期间不接收任何命令，对于SOIC16封装的Flash，
 *      /RESET脚是独立的，不受SR3控制，且有内部上拉，外部不用可以悬空;
 *   6、QPI模式需要在SPI模式下发送38h命令进行切换;
 *   7、Flash支持MODE0(空闲电平为低)和MODE1(空闲电平为高)，空闲电平是指在CS
 *      变换的时候CLK的电平状态，Flash根据/CS拉低时CLK的电平决定采用哪个模式;
 *   8、芯片引脚
 *       +----------+-----+--------------------+
 *       |    /CS   | 1 8 |         VCC        |
 *       |  DO(IO1) | 2 7 | /HOLD /RESET (IO3) |
 *       | /WP(IO2) | 3 6 |         CLK        |
 *       |    GND   | 4 5 |       DI(IO0)      |
 *       +----------+-----+--------------------+
 *   9、SPI1可以工作到42MHz
 *       1、PB14 -- GPIO ------- 软件控制片选
 *       2、PB3 --- SPI1_SCK --- 和JTDO复用，故需要使用SWD调试模式
 *       3、PB4 --- SPI1_MISO -- 和JTRST复用，故需要使用SWD调试模式，需注意调
 *                               试器应当仅接SWD线，其他的线应断开连接，否者
 *                               可能会影响通信
 *       4、PB5 --- SPI1_MOSI
 *   10、SR寄存器有些位支持易变和非易变模式，发送06h(写使能)进行非易变的修改，
 *       修改后掉电仍保留，发送50h(易变写使能)进行易变的修改，修改后掉电会恢
 *       复之前的值。还有一些标志是OTP的，采用非易变写入后将无法再修改
 *   11、最大工作时钟为104MHz，读数据指令(03h)最高时钟为50MHz;
 *   12、/CS和CLK的时钟间隔最少5ns;
 *   13、写SR寄存器的时间10-15ms;
 *   14、字节编程，第一个字节30-50us，后续的字节2.5-12us，页编程0.7-3ms;
 *   16、4KB扇区擦除45-400ms，块32KB擦除120-1600ms，块64KB擦除150-2000ms，
 *       片擦40-200s;
 *
 */

#define W25Q128_SR1_BUSY                 (0x1U<<0)  // 只读，擦除/写入的状态，1表示忙，在忙期间，Flash仅会响应读SR和擦写挂起指令
#define W25Q128_SR1_WEL                  (0x1U<<1)  // 只读，发送写使能指令可设置该位，上电以及执行擦写指令后自动清除该位
#define W25Q128_SR1_BP                   ((0x7U<<2)|_W25Q128_SR1_SEC)  // 易变/非易变，选中区域，CMP用于设置选中的区域被保护，还是非选中的区域被保护，使用写SR指令进行修改，擦写范围包含保护区域的指令会被忽略
#define W25Q128_SR1_BP_NONE              (0x0U<<2)  // 不选中
#define W25Q128_SR1_BP_4KB               ((0x1U<<2)|_W25Q128_SR1_SEC_4KB)  // 选中头或尾连续的4KB空间
#define W25Q128_SR1_BP_8KB               ((0x1U<<2)|_W25Q128_SR1_SEC_4KB)
#define W25Q128_SR1_BP_16KB              ((0x1U<<2)|_W25Q128_SR1_SEC_4KB)
#define W25Q128_SR1_BP_32KB              ((0x1U<<2)|_W25Q128_SR1_SEC_4KB)
#define W25Q128_SR1_BP_256KB             ((0x1U<<2)|_W25Q128_SR1_SEC_64KB)
#define W25Q128_SR1_BP_512KB             ((0x2U<<2)|_W25Q128_SR1_SEC_64KB)
#define W25Q128_SR1_BP_1MB               ((0x3U<<2)|_W25Q128_SR1_SEC_64KB)
#define W25Q128_SR1_BP_2MB               ((0x4U<<2)|_W25Q128_SR1_SEC_64KB)
#define W25Q128_SR1_BP_4MB               ((0x5U<<2)|_W25Q128_SR1_SEC_64KB)
#define W25Q128_SR1_BP_8MB               ((0x6U<<2)|_W25Q128_SR1_SEC_64KB)
#define W25Q128_SR1_BP_ALL               (0x7U<<2)  // 选中全部
#define W25Q128_SR1_TB                   (0x1U<<5)  // 易变/非易变，选中头还是尾
#define W25Q128_SR1_TB_TOP               (0x0U<<5)
#define W25Q128_SR1_TB_BOT               (0x1U<<5)
#define _W25Q128_SR1_SEC                 (0x1U<<6)  // 易变/非易变，选中单位
#define _W25Q128_SR1_SEC_4KB             (0x1U<<6)  // 选中单位是扇区4KB
#define _W25Q128_SR1_SEC_64KB            (0x0U<<6)  // 选中单位是块64KB
#define W25Q128_SR1_SRP0                 (0x1U<<7)  // 状态寄存器保护

/**
 * SR寄存器的写保护
 *    +------+------+-----+-----------------------------------+
 *    | SRP1 | SRP0 | /WP |               描述                |
 *    +------+------+-----+-----------------------------------+
 *    |  0   |  0   |  X  | /WP状态无效，发送写使能后可以写SR |
 *    |  0   |  1   |  0  | /WP为低，SR禁止写                 |
 *    |  0   |  1   |  1  | /WP为高，，发送写使能后可以写SR   |
 *    |  1   |  0   |  X  | 下次上电/复位后，恢复为00         |
 *    |  1   |  1   |  X  | SR被永久保护                      |
 *    +------+------+-----+-----------------------------------+
 * SR寄存器中的保留位可能为1或0，这些位可以写0不受影响
 */
#define W25Q128_SR2_SRP1                 (0x1U<<0)  // 易变/非易变，状态寄存器保护
#define W25Q128_SR2_QE                   (0x1U<<1)  // 易变/非易变
#define W25Q128_SR2_QE_DIS               (0x0U<<1)  // /WP和/HOLD维持原功能
#define W25Q128_SR2_QE_EN                (0x1U<<1)  // /WP和/HOLD分别用作IO2/IO3，进入QPI模式前(38h)，需要先设置该位，否则Flash将忽略38h指令，进入QPI之后，该位将始终为1，无法通过写SR指令将该位清除，QPI模式下发送FFh指令可以切回SPI模式
#define W25Q128_SR2_LB1                  (0x1U<<3)  // 易变/只能写一次，OTP写入后无法通过易变修改，写1安全寄存器1(256B)将永久上锁
#define W25Q128_SR2_LB2                  (0x1U<<4)
#define W25Q128_SR2_LB3                  (0x1U<<5)
#define W25Q128_SR2_CMP                  (0x1U<<6)  // 易变/非易变，保护类型
#define W25Q128_SR2_CMP_NORMAL           (0x0U<<6)  // 选中的区域受保护
#define W25Q128_SR2_CMP_INVERT           (0x1U<<6)  // 非选中的区域受保护
#define W25Q128_SR2_SUS                  (0x1U<<7)  // 只读，擦除/编写暂停指令(75h)会设置该位，擦除/编写恢复指令(7Ah)会清零该位

#define W25Q128_SR3_WPS                  (0x1U<<2)  // 易变/非易变，写保护模式选择，0:使用CMP、SEC、TB、BP进行写保护，1:使用指令对单独的块写保护，开启保护(36h)、关闭保护(39h)、读保护状态(3Dh)、全部锁定(7Eh)、全部解锁(98h)，上电后默认全部锁定
#define W25Q128_SR3_DRV                  (0x3U<<5)  // 易变/非易变，输出IO的驱动力
#define W25Q128_SR3_DRV_100              (0x0U<<5)
#define W25Q128_SR3_DRV_75               (0x1U<<5)
#define W25Q128_SR3_DRV_50               (0x2U<<5)
#define W25Q128_SR3_DRV_25               (0x3U<<5)  // 默认值，25%的驱动力
#define W25Q128_SR3_AF                   (0x1U<<7)  // 易变/非易变，/HOLD和/RESET的复用
#define W25Q128_SR3_AF_HOLD              (0x0U<<7)  // 默认值，复用为/HOLD
#define W25Q128_SR3_AF_RESET             (0x1U<<7)

#define W25Q128_MF_ID                    0xEF    // 厂商ID(Manufacturer Id)
#define W25Q128_DEV_ID                   0x17    // 8bits设备ID
#define W25Q128_JEDEC_SPI                0x4018  // SPI模式下16bits JEDEC ID
#define W25Q128_JEDEC_QPI                0x6018  // QPI模式下16bits JEDEC ID

/**
 * SPI指令集
 *    安全寄存器1地址：[23:8]=0x0010 [7:0]=字节地址
 *    安全寄存器2地址：[23:8]=0x0020 [7:0]=字节地址
 *    安全寄存器3地址：[23:8]=0x0030 [7:0]=字节地址
 *    双线SPI：
 *        IO0 = A22 A20 ... A0 M6 M4 M2 M0
 *        IO1 = A23 A21 ... A1 M7 M5 M3 M1
 *    四线SPI:
 *        IO0 = A20 A16 ... A0 M4 M0
 *        IO1 = A21 A17 ... A1 M5 M1
 *        IO2 = A22 A18 ... A2 M6 M2
 *        IO3 = A23 A19 ... A3 M7 M3
 */
#define W25Q128_CMD_WRITE_ENABLE         0x06  // 写使能，擦写Flash或非易变的形式写SR，掉电后SR保持修改后的值
#define W25Q128_CMD_VOLATILE_SR_WRITE_ENABLE  0x50  // SR易变写使能，掉电后SR恢复之前的值，该命令不会置位WEL，写SR命令之后BUSY不会置起，但SR生效需要50ns
#define W25Q128_CMD_WRITE_DISABLE        0x04  // 写禁止
#define W25Q128_CMD_READ_SR1             0x05  // 读SR1，紧跟着就是收SR1数据，如果继续接收的话SR1的数据仍会继续输出
#define W25Q128_CMD_WRITE_SR1            0x01  // 写SR1，紧跟着就是发SR1数据，如果写SR2的话，也可以继续跟发SR2的数据，非易失写入时间10-15ms
#define W25Q128_CMD_READ_SR2             0x35
#define W25Q128_CMD_WRITE_SR2            0x31
#define W25Q128_CMD_READ_SR3             0x15
#define W25Q128_CMD_WRITE_SR3            0x11
#define W25Q128_CMD_CHIP_ERASE           0xC7  // 或者0x60也可以，参考扇区擦除
#define W25Q128_CMD_SUSPEND              0x75  // 擦除/编程暂停，可以暂停当前扇区/块的擦写操作，去读写擦其他扇区，使用有一些限制，详见手册。SUS=1或BUSY=0时，该命令均被忽略，掉电会丢失该状态信息。/CS拉高后需要20us才能接收新命令
#define W25Q128_CMD_RESUME               0x7A  // 擦除/编程继续
#define W25Q128_CMD_POWER_DOWN           0xB9  // 掉电，进入低功耗，发送完数据后必须将/CS拉高，否者Flash不会进入低功耗。
#define W25Q128_CMD_POWER_UP             0xAB  // 上电，恢复正常，等待3个字节后，收W25Q128_DEV_ID数据，继续则循环W25Q128_DEV_ID，当系统在低功耗时，发送完命令即可拉高/CS，Flash将恢复正常模式，当系统处于正常模式时，发送该命令，可用于读取ID。
#define W25Q128_CMD_MF_ID                0x90  // 读厂商ID，发3字节地址0x000000，收W25Q128_MF_ID，收W25Q128_DEV_ID，如果继续收则循环W25Q128_DEV_ID
#define W25Q128_CMD_JEDEC_ID             0x9F  // JEDEC ID，收W25Q128_MF_ID，收W25Q128_JEDEC_SPI[15:8]，收W25Q128_JEDEC_SPI[7:0]
#define W25Q128_CMD_GLOBAL_BLOCK_LOCK    0x7E  // 全局锁定，WPS=1时有效，该命令前需要先发06h
#define W25Q128_CMD_GLOBAL_BLOCK_UNLOCK  0x98  // 全局解锁，WPS=1时有效
#define W25Q128_CMD_ENTER_QPI            0x38  // 进入QPI模式，切换模式前需要先将SR1.QE=1，切换后可使用QPI命令FFh切回SPI模式，复位后默认进入QPI模式。
#define W25Q128_CMD_ENABLE_RESET         0x66  // 允许复位
#define W25Q128_CMD_RESET                0x99  // 复位，需先发送66h，复位前应先查询SUS和BUSY标记，都为0则可复位，复位后大约需要30us的启动时间。
#define W25Q128_CMD_READ_UID             0x4B  // 获取唯一ID，等待4个字节，收UID[63:0]
#define W25Q128_CMD_PAGE_PGM             0x02  // 页编程，发3字节地址[23:0]，发数据[7:0]，最少发一个数据，当地址是也对齐时，最多发256个数据，地址不对齐时，最多发送页剩余的字节数，否者便会回环到页起始的位置。Flash中有一个256字节的缓存，该命令仅仅将数据写入缓存，发送完数据后必须将/CS拉高，否者Flash不会开始写入操作。发该命令前需要先发06h命令，将WEL设置为1，表示允许写入。如果要写入的地址在保护区，则不会写入，写之后需要读SR1判断BUSY位，BUSY=0表示写入完成，写入完成后WEL=0，表示禁止写入。Flash只能从1写为0，0到1需要擦除。
#define W25Q128_CMD_QUAD_PAGE_PGM        0x32  // 四线页编程，同页编程
#define W25Q128_CMD_SECTOR_ERASE         0x20  // 扇区擦除4KB，发3字节地址，发该命令前需要先发06h命令，将WEL设置为1，表示允许写入。发送完数据后必须将/CS拉高，否者Flash不会开始擦除操作。如果要擦除的地址在保护区，则不会擦除，擦除之后需要读SR1判断BUSY位，BUSY=0表示擦除完成，擦除完成后WEL=0，表示禁止写入。
#define W25Q128_CMD_BLOCK_ERASE_32K      0x52  // 块擦除，32KB，发3字节地址
#define W25Q128_CMD_BLOCK_ERASE_64KB     0xD8  // 块擦除，64KB，发3字节地址
#define W25Q128_CMD_READ_DATA            0x03  // 读数据，发3字节地址，收多字节数据，可以一条指令读出全部数据，因为地址和数据之间没有dummy字节，所以限制SPI最高时钟为50MHz
#define W25Q128_CMD_FAST_READ            0x0B  // 快速读，单线输出，发3字节地址，等待1个字节，收多字节数据，因为地址和数据之间有1个dummy字节，所以时钟可以达到104MHz
#define W25Q128_CMD_FAST_READ_DUAL_OUT   0x3B  // 快速读，双线输出，发3字节地址，等待1个字节，收多字节数据，指令发送以及dummy都是单线的，之后的数据是双线的
#define W25Q128_CMD_FAST_READ_QUAD_OUT   0x6B  // 快速读，四线输出，发3字节地址，等待1个字节，收多字节数据
#define W25Q128_CMD_READ_SFDP            0x5A  // 串行Flash参数描述寄存器(Serial Flash Discoverable Parameter)，是一个计算机存储行业标准，该表中描述了，Flash支持的指令、配置参数等信息，发3字节地址[23:8]=0，[7:0]=SFDP寄存器的偏移地址，等待1个字节，收多字节数据
#define W25Q128_CMD_SECURITY_ERASE       0x44  // 擦除安全寄存器，发3字节地址，安全寄存器n地址：[23:8]=0x00n0 [7:0]无意义（n=1,2,3），需要先发06h使能写入。
#define W25Q128_CMD_SECURITY_PGM         0x42  // 编程安全寄存器，发3字节地址，安全寄存器n地址：[23:8]=0x00n0 [7:0]=字节地址（n=1,2,3）发数据[7:0]，最少发一个数据，最多发256个数据，超出后会回环到起始处，覆盖之前写的数据
#define W25Q128_CMD_SECURITY_READ        0x48  // 读安全寄存器，发3字节地址，等待1个字节，收多字节数据，如果数据读到结尾，则会从头开始。
#define W25Q128_CMD_BLOCK_LOCK           0x36  // 锁定指定块，WPS=1时有效，发3字节地址，该设置在复位后自动丢失，复位后默认所有的块被锁定，该命令前需发送06h
#define W25Q128_CMD_BLOCK_UNLOCK         0x39  // 解锁指定块，WPS=1时有效，发3字节地址
#define W25Q128_CMD_READ_BLOCK_LOCK      0x3D  // 读取块锁定信息，WPS=1时有效，发3字节地址，收1字节数据，数据中仅最低位有意义，0表示解锁，1表示上锁
/**
 * 双线命令
 */
#define W25Q128_CMD_FAST_READ_DUAL       0xBB  // 双线快速读，发3字节地址，发1个Mode字节，收多字节数据。指令是单线发送，地址和Mode字节以及后续的数据都是双线的，当设置Mode[5:4]=10时，下次拉低/CS后可以不发送BBh直接发3字节地址，发1个Mode字节，收多字节数据，当某一帧中设置Mode[5:4]!=10，则下一次拉低/CS后需要先发命令。Mode[3:0]无意义，在第一个数据输出前，主机应将相关的IO设置为高阻态。该命令主要是为了加快随机访问，用在XIP场景中。推荐结束连续(Mode[5:4]=10)操作时，将地址和Mode都设置为0xFF。
#define W25Q128_CMD_MF_ID_DUAL           0x92  // 双线读厂商ID，发3字节地址0x000000，发1个Mode字节，收W25Q128_MF_ID，收W25Q128_DEV_ID，循环W25Q128_MF_ID和W25Q128_DEV_ID，Mode字节必须设置为0xFx，也即参考快速读中的Mode位。
/**
 * 四线命令
 */
#define W25Q128_CMD_WRAP                 0x77  // 设置突发长度，等待3个字节，发1个Wrap字节，Wrap[4]=0表示使能，Wrap[4]=1表示禁止，Wrap[6:5]=00表示8字节，01:16字节，10:32字节，11:64字节，Wrap[3:0]无意义。指令是单线的，dummy和Wrap都是4线的。用于该命令之后的FAST_READ_QUAD(EBh)和WORD_READ_QUAD(E7h)都遵守Wrap规则。直到重新发送WRAP指令(77h)，Flash复位后Wrap是禁用的。使能Wrap后，FAST_READ_QUAD读出BURST字节(8/16/32/64)后继续读取则会返回都起始地址处，禁用Wrap后，FAST_READ_QUAD持续读，数据将一直向后延申直到末尾。推测一种场景是，CPU访问了某个Cache Line中间的数据，QSPI就直接发送了这个地址，并且连续读了Cache Line长度的数据，这样Wrap机制就可以将整个Cache Line的数据返回，即先发后半截，再发前半截。
#define W25Q128_CMD_FAST_READ_QUAD       0xEB  // 四线快速读，发3字节地址，发1个Mode字节，等待2个字节，收多字节数据，比双线多了2个dummy，dummy也是四线发送，其他的相似，需注意有时候QSPI配置时会将Mode字节也当做dummy处理了，QSPI处理dummy时通常是保持之前的电平，所以可能会导致Mode被误配置。
#define W25Q128_CMD_WORD_READ_QUAD       0xE7  // 四线读word，发3字节地址，发1个Mode字节，等待1个字节，收多字节数据，和FAST_READ_QUAD相似，但仅需1个dummy字节，要求地址的最低位A[0:0]=0，即2字节对齐，该指令其他品牌可能不支持。
#define W25Q128_CMD_OCTAL_WORD_READ_QUAD 0xE3  // 四线读8word，发3字节地址，发1个Mode字节，收多字节数据，和FAST_READ_QUAD相似，但不需要dummy字节，要求地址的低四位A[3:0]=0000，即16字节对齐，该指令其他品牌可能不支持。
#define W25Q128_CMD_MF_ID_QUAD           0x94  // 四线读厂商ID，发3字节地址0x000000，发1个Mode字节，等待2个字节，收W25Q128_MF_ID，收W25Q128_DEV_ID，Mode字节必须设置为0xFx，也即参考快速读中的Mode位。

#define W25Q128_PAGE        (256U)
#define W25Q128_SECTOR      (1024U*4)
#define W25Q128_BLOCK1      (1024U*32U)
#define W25Q128_BLOCK2      (1024U*64U)

static void mSpiClockInit(void)
{
	/* 使能SPI1时钟 */
	RCC_EnableApb2(RCC_CLK_APB2_SPI1);
	RCC_EnableAhb1(RCC_CLK_AHB1_GPIOB);
}

static void mSpiGpioInit(void)
{
	uint32_t tmp32;

	/* 复用模式 */
	tmp32 = SOC_GPIOB.MODER;
	tmp32 &= ~GPIO_MODER_MASK(3);
	tmp32 &= ~GPIO_MODER_MASK(4);
	tmp32 &= ~GPIO_MODER_MASK(5);
	tmp32 &= ~GPIO_MODER_MASK(14);
	tmp32 |= GPIO_MODER_AF(3);
	tmp32 |= GPIO_MODER_AF(4);
	tmp32 |= GPIO_MODER_AF(5);
	tmp32 |= GPIO_MODER_OUT(14);
	SOC_GPIOB.MODER = tmp32;

	/* CS/SCK/MOSI推挽输出 */
	tmp32 = SOC_GPIOB.OTYPER;
	tmp32 &= ~GPIO_OTYPER_MASK(3);
	tmp32 &= ~GPIO_OTYPER_MASK(5);
	tmp32 &= ~GPIO_OTYPER_MASK(14);
	tmp32 |= GPIO_OTYPER_PP(3);
	tmp32 |= GPIO_OTYPER_PP(5);
	tmp32 |= GPIO_OTYPER_PP(14);
	SOC_GPIOB.OTYPER = tmp32;

	/* 50M的输出速度 */
	tmp32 = SOC_GPIOB.OSPEEDR;
	tmp32 &= ~GPIO_OSPEEDR_MASK(3);
	tmp32 &= ~GPIO_OSPEEDR_MASK(5);
	tmp32 &= ~GPIO_OSPEEDR_MASK(14);
	tmp32 |= GPIO_OSPEEDR_50M(3);
	tmp32 |= GPIO_OSPEEDR_50M(5);
	tmp32 |= GPIO_OSPEEDR_50M(14);
	SOC_GPIOB.OSPEEDR = tmp32;

	/* 禁用上下拉 */
	tmp32 = SOC_GPIOB.PUPDR;
	tmp32 &= ~GPIO_PUPDR_MASK(3);
	tmp32 &= ~GPIO_PUPDR_MASK(4);
	tmp32 &= ~GPIO_PUPDR_MASK(5);
	tmp32 &= ~GPIO_PUPDR_MASK(14);
	tmp32 |= GPIO_PUPDR_NONE(3);
	tmp32 |= GPIO_PUPDR_NONE(4);
	tmp32 |= GPIO_PUPDR_NONE(5);
	tmp32 |= GPIO_PUPDR_NONE(14);
	SOC_GPIOB.PUPDR = tmp32;

	/* IO复用为SPI1 */
	tmp32 = SOC_GPIOB.AFLR;
	tmp32 &= ~GPIO_AFLR_MASK(3);
	tmp32 &= ~GPIO_AFLR_MASK(4);
	tmp32 &= ~GPIO_AFLR_MASK(5);
	tmp32 |= GPIO_AFLR_0_15D(3, 5);
	tmp32 |= GPIO_AFLR_0_15D(4, 5);
	tmp32 |= GPIO_AFLR_0_15D(5, 5);
	SOC_GPIOB.AFLR = tmp32;

	/* PB14用于普通输出，其实不用配置AF */
	tmp32 = SOC_GPIOB.AFHR;
	tmp32 &= ~GPIO_AFHR_MASK(14);
	tmp32 |= GPIO_AFHR_0_15D(14, 0);
	SOC_GPIOB.AFHR = tmp32;

	/* PB14=1，CS拉高 */
	SOC_GPIOB.BSRR = GPIO_BSRR_SET(14);
}

static void mSpiDeviceInit(void)
{
	/* 使能SPI模式 */
	SOC_SPI1.I2SCFGR = SPI_I2SCFGR_I2SMOD_SPI;
	/* SPI配置 */
	SOC_SPI1.CR1 = SPI_CR1_CPOL_LOW |       // 空闲电平为低
	               SPI_CR1_CPHA_FIRST |     // 第一个边沿采样数据，也即上升
	                                        // 沿采样
	               SPI_CR1_MSTR_MASTER |    // 主机模式
	               SPI_CR1_BR_DIV2 |        // 2分频最大为42MHz
	               SPI_CR1_SPE_DIS |        // 启用SPI，全双工收发模式下，
	                                        // DR写入数据才会启动通信流程，
	                                        // 若RXONLY_EN，则SPE_EN则启动
	                                        // 通信流程
	               SPI_CR1_LSBFIRST_DIS |   // 先发送高BIT
	               SPI_CR1_SS_HIGH |        // 软件控制CS，MASTER下NSS信号
	                                        // 必须强制为高
	               SPI_CR1_RXONLY_DIS |     // 全双工模式
	               SPI_CR1_DFF_8B |         // 8bits数据帧
	               SPI_CR1_CRCEN_DIS |      // 不使用CRC
	               SPI_CR1_BIDIMODE_NORMAL; // 普通SPI，双线模式
	SOC_SPI1.CR2 = SPI_CR2_RXDMAEN_DIS |    // RX DMA禁止
	               SPI_CR2_TXDMAEN_DIS |    // TX DMA禁止
	               SPI_CR2_SSOE_DIS |       // 在主模式下禁止SS输出，使用软件
	                                        // CS，该位无意义
	               SPI_CR2_FRF_SPI |        // 普通SPI，非TI兼容模式
	               SPI_CR2_ERRIE_DIS |      // 错误中断禁止
	               SPI_CR2_RXNEIE_DIS |     // 接收中断禁止
	               SPI_CR2_TXEIE_DIS;       // 发送中断禁止
	/* 保险起见，配置完毕后再使能SPI */
	SOC_SPI1.CR1 |= SPI_CR1_SPE_EN;
}

static void mSpiInterrupt(void)
{
	NVIC_SetPriority(NVIC_IRQ_SPI1, NVIC_PRIORITY_LOW,
	                 NVIC_PRIORITY_SUB_NONE);
	NVIC_EnableIrq(NVIC_IRQ_SPI1);
}

static void mSpiDmaInit(void)
{
}

static void mSpiCsEnable(void)
{
	/* PB14=0 */
	SOC_GPIOB.BSRR = GPIO_BSRR_CLEAR(14);
}

static void mSpiCsDisable(void)
{
	/* PB14=1 */
	SOC_GPIOB.BSRR = GPIO_BSRR_SET(14);
}

void mSpiInit(void)
{
	mSpiClockInit();
	mSpiGpioInit();
	mSpiDeviceInit();
	mSpiInterrupt();
	mSpiDmaInit();
}

static void mSpiTx(const uint8_t *tx, uint32_t txLen)
{
	uint32_t i;

	/* 拉低CS */
	mSpiCsEnable();

	/* 循环发送数据，忽略接收的数据 */
	for (i = 0; i < txLen; i++) {
		SOC_SPI1.DR = tx[i];
		while (!(SOC_SPI1.SR & SPI_SR_TXE_EN));
	}
	/* 等待最后一个数据发送完成，因为此时接收的数据仍然是不需要的 */
	while (SOC_SPI1.SR & SPI_SR_BSY_EN);

	/* 拉高CS */
	mSpiCsDisable();
}

static void mSpiTxData(const uint8_t *cmd, uint32_t cmdLen,
                       const uint8_t *dat, uint32_t datLen)
{
	uint32_t i;

	/* 拉低CS */
	mSpiCsEnable();

	/* 循环发送数据，忽略接收的数据 */
	for (i = 0; i < cmdLen; i++) {
		SOC_SPI1.DR = cmd[i];
		while (!(SOC_SPI1.SR & SPI_SR_TXE_EN));
	}
	for (i = 0; i < datLen; i++) {
		SOC_SPI1.DR = dat[i];
		while (!(SOC_SPI1.SR & SPI_SR_TXE_EN));
	}
	/* 等待最后一个数据发送完成，因为此时接收的数据仍然是不需要的 */
	while (SOC_SPI1.SR & SPI_SR_BSY_EN);

	/* 拉高CS */
	mSpiCsDisable();
}

/**
 * 当SPI1工作在42MHz时，该函数执行不及时会导致数据丢失
 */
#if 0
static void _mSpiTxThenRx(const uint8_t *tx, uint32_t txLen, uint8_t *rx,
                         uint32_t rxLen)
{
	uint32_t i;
	uint8_t *rxEnd = rx + rxLen;

	/* 拉低CS */
	mSpiCsEnable();

	/* 循环发送数据，忽略接收的数据 */
	for (i = 0; i < txLen; i++) {
		SOC_SPI1.DR = tx[i];
		while (!(SOC_SPI1.SR & SPI_SR_TXE_EN));
	}
	/* 等待最后一个数据发送完成，因为此时接收的数据仍然是不需要的 */
	while (SOC_SPI1.SR & SPI_SR_BSY_EN);
	/* 读一次接收的数据，否则后续接收的数据不会更新到DR寄存器，再读SR用于
	   清除溢出标志 */
	SOC_SPI1.DR;
	SOC_SPI1.SR;

	/* 循环接收数据 */
	for (i = 0; i < rxLen; i++) {
		/* 写dummy数据，可以是任意值 */
		SOC_SPI1.DR = 0x00;
		while (!(SOC_SPI1.SR & SPI_SR_TXE_EN));
		/* 发送为空仅代表数据移到了移位寄存器，此时数据还没有发送完毕，
		   接收数据为空 */
		if (SOC_SPI1.SR & SPI_SR_RXNE_EN) {
			*rx++ = SOC_SPI1.DR;
		}
	}
	/* 检擦数据是否接收完毕，此处主要是防止接收过程中被其他事务长时间打断 */
	if (rx != rxEnd) {
		/* 等待最后一个数据接收完毕 */
		while (!(SOC_SPI1.SR & SPI_SR_RXNE_EN));
		*rx = SOC_SPI1.DR;
	}

	/* 拉高CS */
	mSpiCsDisable();
}
#endif

/**
 * 该函数可以工作在42MHz
 */
static void mSpiTxThenRx(const uint8_t *tx, uint32_t txLen, uint8_t *rx,
                         uint32_t rxLen)
{
	uint32_t i;

	/* 拉低CS */
	mSpiCsEnable();

	/* 循环发送数据，忽略接收的数据 */
	for (i = 0; i < txLen; i++) {
		SOC_SPI1.DR = tx[i];
		while (!(SOC_SPI1.SR & SPI_SR_TXE_EN));
	}
	/* 等待最后一个数据发送完成，因为此时接收的数据仍然是不需要的 */
	while (SOC_SPI1.SR & SPI_SR_BSY_EN);
	/* 读一次接收的数据，否则后续接收的数据不会更新到DR寄存器，可再读SR用于
	   清除溢出标志 */
	SOC_SPI1.DR;

	/* 循环接收数据 */
	for (i = 0; i < rxLen; i++) {
		/* 写dummy数据，可以是任意值 */
		SOC_SPI1.DR = 0x00;
		/* 因为发送的速度很快，故发一个收一个，避免数据被覆盖 */
		while (!(SOC_SPI1.SR & SPI_SR_RXNE_EN));
		*rx++ = SOC_SPI1.DR;
	}

	/* 拉高CS */
	mSpiCsDisable();
}

static void mSpiFlashWait(void)
{
	/* 拉低CS */
	mSpiCsEnable();

	/* 读SR1命令 */
	SOC_SPI1.DR = W25Q128_CMD_READ_SR1;
	/* 等待命令发送完毕 */
	while (!(SOC_SPI1.SR & SPI_SR_RXNE_EN));
	/* dummy读 */
	SOC_SPI1.DR;

	/* 循环读SR1寄存器的值 */
	do {
		/* 写dummy数据，可以是任意值 */
		SOC_SPI1.DR = 0x00;
		while (!(SOC_SPI1.SR & SPI_SR_RXNE_EN));
	} while (SOC_SPI1.DR & W25Q128_SR1_BUSY);

	/* 拉高CS */
	mSpiCsDisable();
}

static void mSpiFlashWriteOnce(uint32_t addr, const uint8_t *buf, uint32_t len)
{
	int i;
	const uint8_t *cmd = (const uint8_t *)&addr;

	/* 拉低CS */
	mSpiCsEnable();
	/* 允许写命令 */
	SOC_SPI1.DR = W25Q128_CMD_WRITE_ENABLE;
	/* 需要先等待发送为空，再等待BUSY */
	while (!(SOC_SPI1.SR & SPI_SR_TXE_EN));
	while (SOC_SPI1.SR & SPI_SR_BSY_EN);
	/* 拉高CS */
	mSpiCsDisable();

	addr |= (uint32_t)W25Q128_CMD_PAGE_PGM << 24;

	/* 拉低CS */
	mSpiCsEnable();

	for (i = 3; i >= 0; i--) {
		/* 等待发送为空 */
		SOC_SPI1.DR = cmd[i];
		while (!(SOC_SPI1.SR & SPI_SR_TXE_EN));
	}
	/* 发送数据 */
	while (len--) {
		SOC_SPI1.DR = *buf++;
		while (!(SOC_SPI1.SR & SPI_SR_TXE_EN));
	}
	/* 等待最后一个数据发送完成，因为此时接收的数据仍然是不需要的 */
	while (SOC_SPI1.SR & SPI_SR_BSY_EN);

	/* 拉高CS */
	mSpiCsDisable();

	/* 等待擦除完成，擦除命令之后必须拉高CS才会开始擦除 */
	mSpiFlashWait();
}

static void mSpiFlashEraseOnce(uint32_t opt)
{
	int i;
	const uint8_t *cmd = (const uint8_t *)&opt;

	/* 拉低CS */
	mSpiCsEnable();
	/* 允许写命令 */
	SOC_SPI1.DR = W25Q128_CMD_WRITE_ENABLE;
	/* 需要先等待发送为空，再等待BUSY */
	while (!(SOC_SPI1.SR & SPI_SR_TXE_EN));
	while (SOC_SPI1.SR & SPI_SR_BSY_EN);
	/* 拉高CS */
	mSpiCsDisable();

	/* 拉低CS */
	mSpiCsEnable();

	for (i = 3; i >= 0; i--) {
		/* 等待发送为空 */
		SOC_SPI1.DR = cmd[i];
		while (!(SOC_SPI1.SR & SPI_SR_TXE_EN));
	}
	/* 等待最后一个数据发送完成，因为此时接收的数据仍然是不需要的 */
	while (SOC_SPI1.SR & SPI_SR_BSY_EN);

	/* 拉高CS */
	mSpiCsDisable();

	/* 等待擦除完成，擦除命令之后必须拉高CS才会开始擦除 */
	mSpiFlashWait();
}

void mSpiFlashInfo(void)
{
	uint8_t dat[256];
	uint16_t jedec;

	memset(dat, 0xAA, 256);

	/* 读厂商ID：发0x90，发3字节地址(0x000000)，收1字节MF_ID，收1字节DEV_ID */
	const uint8_t cmd0[] = {W25Q128_CMD_MF_ID, 0x00, 0x00, 0x00};
	mSpiTxThenRx(cmd0, sizeof(cmd0), dat, 2);

	/* 读JEDEC ID：发0x9F，收1字节MF_ID，收2字节JEDEC_SPI */
	const uint8_t cmd1[] = {W25Q128_CMD_JEDEC_ID};
	mSpiTxThenRx(cmd1, sizeof(cmd1), &dat[2], 3);

	/* 收到的JEDEC_ID数据是大端格式，转换为小端 */
	jedec = dat[3]; jedec <<= 8; jedec |= dat[4];
	mDebugPrintf("[厂商ID]: %02X [设备ID]: %02X [JEDEC ID]: %04X" MDEBUG_ENDL,
	             (uint32_t)dat[0], (uint32_t)dat[1], (uint32_t)jedec);

	/* 检查JEDEC的值 */
	if ((jedec != W25Q128_JEDEC_SPI) || (dat[0] != W25Q128_MF_ID) ||
	    (dat[1] != W25Q128_DEV_ID)) {
		while (1);
	}
	
	/* 读唯一ID：发0x4B，等4个字节，收8个字节 */
	const uint8_t cmd2[] = {W25Q128_CMD_READ_UID, 0x00, 0x00, 0x00, 0x00};
	mSpiTxThenRx(cmd2, sizeof(cmd2), dat, 8);
	mDebugPutMemoryForU8("[唯一ID]: ", dat, 8);

	/* 读配置参数寄存器：发0x5A，发3字节地址[23:8]=0 [7:0]偏移，收多字节数据 */
	const uint8_t cmd3[] = {W25Q128_CMD_READ_SFDP, 0x00, 0x00, 0x00};
	mSpiTxThenRx(cmd3, sizeof(cmd3), dat, 256);
	mDebugHexDumpU8(dat, 256, 32, "%03X: ", 0);

	/* 读SR1寄存器：发0x05，收1字节数据 */
	const uint8_t cmd4[] = {W25Q128_CMD_READ_SR1};
	mSpiTxThenRx(cmd4, sizeof(cmd4), dat, 1);
	const uint8_t cmd5[] = {W25Q128_CMD_READ_SR2};
	mSpiTxThenRx(cmd5, sizeof(cmd5), &dat[1], 1);
	const uint8_t cmd6[] = {W25Q128_CMD_READ_SR3};
	mSpiTxThenRx(cmd6, sizeof(cmd6), &dat[2], 1);
	mDebugPrintf("[SR寄存器]: %02X %02X %02X\n", dat[0], dat[1], dat[2]);
}

void mSpiFlashRead(uint32_t addr, uint8_t *buf, uint32_t len)
{
	addr |= 0x03U<<24;
	_mDevU32EndianSwap(&addr, &addr, 1);
	mSpiTxThenRx((uint8_t *)&addr, 4, buf, len);
}

void mSpiFlashWrite(uint32_t addr, const uint8_t *buf, uint32_t len)
{
	uint32_t tmp1;
	uint32_t tmp2;

	/* 起始地址是否PAGE对齐 */
	tmp1 = addr & (W25Q128_PAGE-1);
	if (tmp1) {
		/* 计算当前PAGE剩余空间 */
		tmp2 = W25Q128_PAGE - tmp1;
		if (tmp2 >= len) {
			/* 剩余空间足够，则一次性写完 */
			mSpiFlashWriteOnce(addr, buf, len);
			return;
		} else {
			/* 写满当前PAGE */
			mSpiFlashWriteOnce(addr, buf, tmp2);
			buf += tmp2;
			len -= tmp2;
			addr += tmp2;
		}
	}
	/* 整PAGE写入 */
	while (len > W25Q128_PAGE) {
		mSpiFlashWriteOnce(addr, buf, W25Q128_PAGE);
		buf += W25Q128_PAGE;
		len -= W25Q128_PAGE;
		addr += W25Q128_PAGE;
	}
	/* 如未写完，则写入剩余部分 */
	if (len) {
		mSpiFlashWriteOnce(addr, buf, len);
	}
}

void mSpiFlashErase(uint32_t addr, uint32_t len)
{
	uint8_t *bak = NULL;
	uint32_t start = addr;
	uint32_t end = start + len;
	uint32_t tmp1;
	uint32_t tmp2;

	/* 起始地址是否SECTOR对齐 */
	tmp1 = start & (W25Q128_SECTOR-1);
	if (tmp1) {
		/* 起始地址不对齐，需要拷贝 */
		bak = umm_malloc(W25Q128_SECTOR);
		start -= tmp1;
		tmp2 = start + W25Q128_SECTOR;
		/* 读回SECTOR前面的数据 */
		mSpiFlashRead(start, bak, tmp1);
		tmp2 -= end;
		if ((tmp2 > 0) && (tmp2 < W25Q128_SECTOR)) {
			/* 如果结束地址在同一个SECTOR，则读回SECTOR后面的数据 */
			mSpiFlashRead(end, &bak[tmp1], tmp2);
		}
		/* 擦除一个SECTOR */
		mSpiFlashEraseOnce(((uint32_t)W25Q128_CMD_SECTOR_ERASE<<24) | start);
		/* 写回SECTOR前面的数据 */
		mSpiFlashWrite(start, bak, tmp1);
		if (tmp2 < W25Q128_SECTOR) {
			if (tmp2 > 0) {
				/* 如果结束地址在同一个SECTOR，则写回SECTOR后面的数据 */
				mSpiFlashWrite(end, &bak[tmp1], tmp2);
			}
			umm_free(bak);
			return;
		}
		start += W25Q128_SECTOR;
	}

	/* 结束地址是否SECTOR对齐 */
	tmp1 = end & (W25Q128_SECTOR-1);
	if (tmp1) {
		if (bak == NULL) {
			bak = umm_malloc(W25Q128_SECTOR);
		}
		tmp2 = W25Q128_SECTOR - tmp1;
		mSpiFlashRead(end, bak, tmp2);
		mSpiFlashEraseOnce(((uint32_t)W25Q128_CMD_SECTOR_ERASE<<24) | end);
		mSpiFlashWrite(end, bak, tmp2);
		end -= tmp1;
	}

	for (; start < end; start += W25Q128_SECTOR) {
		mSpiFlashEraseOnce(((uint32_t)W25Q128_CMD_SECTOR_ERASE<<24) | start);
	}
	if (bak) {
		umm_free(bak);
	}
}

void mSpiTest(void)
{
	uint32_t i;
	uint8_t dat[256];

	mSpiFlashInfo();

	/* 读数据：发0x03，发3字节地址，收多字节数据 */
	const uint8_t cmd0[] = {W25Q128_CMD_READ_DATA, 0x00, 0x00, 0x00};
	mSpiTxThenRx(cmd0, sizeof(cmd0), dat, 32);
	mDebugPutMemoryForU8("读: ", dat, 32);
	
	/* 允许写：发0x06 */
	const uint8_t cmd1[] = {W25Q128_CMD_WRITE_ENABLE};
	mSpiTx(cmd1, sizeof(cmd1));

	/* 读SR1寄存器：发0x05，收1字节数据 */
	const uint8_t cmd2[] = {W25Q128_CMD_READ_SR1};
	mSpiTxThenRx(cmd2, sizeof(cmd2), dat, 1);
	mDebugPrintf("SR1: %02X\n", dat[0]);

	/* 擦扇区：发0x20，发3字节地址 */
	const uint8_t cmd3[] = {W25Q128_CMD_SECTOR_ERASE, 0x00, 0x00, 0x00};
	mSpiTx(cmd3, sizeof(cmd3));

	/* 读SR1，等待BUSY */
	do {
		mSpiTxThenRx(cmd2, sizeof(cmd2), dat, 1);
	} while (dat[0] & W25Q128_SR1_BUSY);

	/* 读数据 */
	mSpiTxThenRx(cmd0, sizeof(cmd0), dat, 32);
	mDebugPutMemoryForU8("读: ", dat, 32);

	/* 允许写 */
	mSpiTx(cmd1, sizeof(cmd1));

	/* 写数据：发0x02，发3字节地址，发多字节数据 */
	const uint8_t cmd4[] = {W25Q128_CMD_PAGE_PGM, 0x00, 0x00, 0x00};
	for (i = 0; i < sizeof(dat); i++) {
		dat[i] = i;
	}
	mSpiTxData(cmd4, sizeof(cmd4), dat, sizeof(dat));

	/* 读SR1，等待BUSY */
	do {
		mSpiTxThenRx(cmd2, sizeof(cmd2), dat, 1);
	} while (dat[0] & W25Q128_SR1_BUSY);

	/* 读数据 */
	mSpiTxThenRx(cmd0, sizeof(cmd0), dat, 32);
	mDebugPutMemoryForU8("读: ", dat, 32);

	mDebugPuts("读数据：");
	mSpiFlashRead(0x000000, dat, 32);
	mDebugPutMemoryForU8("000000: ", dat, 32);
	mSpiFlashRead(0x0001F0, dat, 32);
	mDebugPutMemoryForU8("0001F0: ", dat, 32);
	mSpiFlashRead(0x000FF0, dat, 32);
	mDebugPutMemoryForU8("000FF0: ", dat, 32);

	mDebugPuts("擦除：");
	mSpiFlashErase(0x000000, 4096);
	mSpiFlashRead(0x000000, dat, 32);
	mDebugPutMemoryForU8("000000: ", dat, 32);
	mSpiFlashRead(0x0001F0, dat, 32);
	mDebugPutMemoryForU8("0001F0: ", dat, 32);
	mSpiFlashRead(0x000FF0, dat, 32);
	mDebugPutMemoryForU8("000FF0: ", dat, 32);

	mDebugPuts("写数据：");
	memset(dat, 0x1, 256);
	mSpiFlashWrite(0x000000, dat, 256);
	memset(dat, 0x2, 256);
	mSpiFlashRead(0x000000, dat, 256);
	mDebugHexDumpU8(dat, 256, 32, "%06X: ", 0);
	memset(dat, 0x3, 256);
	mSpiFlashRead(0x0000F0, dat, 32);
	mDebugPutMemoryForU8("0000F0: ", dat, 32);
	memset(dat, 0x4, 256);
	mSpiFlashWrite(0x0002FC, dat, 16);
	memset(dat, 0x5, 256);
	mSpiFlashRead(0x0002F0, dat, 32);
	mDebugPutMemoryForU8("0002F0: ", dat, 32);
	memset(dat, 0x6, 256);
	mSpiFlashWrite(0x0003FE, dat, 13);
	memset(dat, 0x7, 256);
	mSpiFlashRead(0x0003F0, dat, 32);
	mDebugPutMemoryForU8("0003F0: ", dat, 32);
	memset(dat, 0x8, 256);
	mSpiFlashWrite(0x000F0E, dat, 225);
	memset(dat, 0x9, 256);
	mSpiFlashRead(0x000F00, dat, 256);
	mDebugHexDumpU8(dat, 256, 32, "%06X: ", 0x000F00);

	mDebugPuts("擦除：");
	mSpiFlashErase(0x000000, 1);
	mSpiFlashRead(0x000000, dat, 32);
	mDebugPutMemoryForU8("000000: ", dat, 32);
	mSpiFlashErase(0x000003, 11);
	mSpiFlashRead(0x000000, dat, 32);
	mDebugPutMemoryForU8("000000: ", dat, 32);
	mSpiFlashErase(0x000FFE, 5);
	mSpiFlashRead(0x000FE0, dat, 256);
	mDebugHexDumpU8(dat, 256, 32, "%06X: ", 0x000FE0);
}
